package com.dyna.core.utils;

import com.dyna.constants.CharPool;
import com.dyna.core.exception.CustomException;
import com.dyna.core.exception.Exceptions;
import com.dyna.domain.dto.FileNode;
import lombok.experimental.UtilityClass;
import lombok.extern.slf4j.Slf4j;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.FileSystemUtils;
import org.springframework.util.PatternMatchUtils;
import org.springframework.web.multipart.MultipartFile;

import java.io.*;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.text.DecimalFormat;
import java.util.*;
import java.util.stream.Collectors;

/**
 * 文件工具类
 * @author zcw
 */
@Slf4j
@UtilityClass
public class FileUtil extends org.springframework.util.FileCopyUtils {

	/**
	 * 默认为true
	 */
	public static class TrueFilter implements FileFilter, Serializable {
		private static final long serialVersionUID = -6420452043795072619L;

		public final static TrueFilter TRUE = new TrueFilter();

		@Override
		public boolean accept(File pathname) {
			return true;
		}
	}

	/**
	 * 扫描目录下的文件
	 * @param path 路径
	 * @return 文件集合
	 */
	public static List<File> list(String path) {
		File file = new File(path);
		return list(file, TrueFilter.TRUE);
	}

	/**
	 * 扫描目录下的文件
	 * @param path   路径
	 * @param fileNamePattern 文件名 * 号
	 * @return 文件集合
	 */
	public static List<File> list(String path, final String fileNamePattern) {
		File file = new File(path);
		return list(file, pathname -> {
			String fileName = pathname.getName();
			return PatternMatchUtils.simpleMatch(fileNamePattern, fileName);
		});
	}

	/**
	 * 扫描目录下的文件
	 * @param path   路径
	 * @param filter 文件过滤
	 * @return 文件集合
	 */
	public static List<File> list(String path, FileFilter filter) {
		File file = new File(path);
		return list(file, filter);
	}

	/**
	 * 扫描目录下的文件
	 *
	 * @param file 文件
	 * @return 文件集合
	 */
	public static List<File> list(File file) {
		List<File> fileList = new ArrayList<>();
		return list(file, fileList, TrueFilter.TRUE);
	}

	/**
	 * 扫描目录下的文件
	 *
	 * @param file   文件
	 * @param fileNamePattern Spring AntPathMatcher 规则
	 * @return 文件集合
	 */
	public static List<File> list(File file, final String fileNamePattern) {
		List<File> fileList = new ArrayList<>();
		return list(file, fileList, pathname -> {
			String fileName = pathname.getName();
			return PatternMatchUtils.simpleMatch(fileNamePattern, fileName);
		});
	}

	/**
	 * 扫描目录下的文件
	 *
	 * @param file   文件
	 * @param filter 文件过滤
	 * @return 文件集合
	 */
	public static List<File> list(File file, FileFilter filter) {
		List<File> fileList = new ArrayList<>();
		return list(file, fileList, filter);
	}

	/**
	 * 扫描目录下的文件
	 *
	 * @param file   文件
	 * @param filter 文件过滤
	 * @return 文件集合
	 */
	private static List<File> list(File file, List<File> fileList, FileFilter filter) {
		if (file.isDirectory()) {
			File[] files = file.listFiles();
			if (files != null) {
				for (File f : files) {
					list(f, fileList, filter);
				}
			}
		} else {
			// 过滤文件
			boolean accept = filter.accept(file);
			if (file.exists() && accept) {
				fileList.add(file);
			}
		}
		return fileList;
	}

	/**
	 * 获取文件后缀名
	 * @param fullName 文件全名
	 * @return {String}
	 */
	public static String getFileExtension(String fullName) {
		Assert.notNull(fullName, "file fullName is null.");
		String fileName = new File(fullName).getName();
		int dotIndex = fileName.lastIndexOf('.');
		return (dotIndex == -1) ? "" : fileName.substring(dotIndex + 1);
	}

	/**
	 * 获取文件名，去除后缀名
	 * @param fullName 文件
	 * @return {String}
	 */
	public static String getFilePreName(String fullName) {
		Assert.notNull(fullName, "file is null.");
		String fileName = new File(fullName).getName();
		int dotIndex = fileName.lastIndexOf(CharPool.DOT);
		return (dotIndex == -1) ? fileName : fileName.substring(0, dotIndex);
	}

	/**
	 * Reads the contents of a file into a String.
	 * The file is always closed.
	 *
	 * @param file the file to read, must not be {@code null}
	 * @return the file contents, never {@code null}
	 */
	public static String readToString(final File file) {
		return readToString(file, Charsets.UTF_8);
	}

	/**
	 * 将文件的内容读入 String 中。
	 * The file is always closed.
	 *
	 * @param file     the file to read, must not be {@code null}
	 * @param encoding the encoding to use, {@code null} means platform default
	 * @return the file contents, never {@code null}
	 */
	public static String readToString(final File file, final Charset encoding) {
		try (InputStream in = Files.newInputStream(file.toPath())) {
			return IoUtil.toString(in, encoding);
		} catch (IOException e) {
			throw Exceptions.unchecked(e);
		}
	}

	/**
	 * Writes a String to a file creating the file if it does not exist.
	 *
	 * @param file the file to write
	 * @param data the content to write to the file
	 */
	public static void writeToFile(final File file, final String data) {
		writeToFile(file, data, Charsets.UTF_8, false);
	}

	/**
	 * Writes a String to a file creating the file if it does not exist.
	 *
	 * @param file   the file to write
	 * @param data   the content to write to the file
	 * @param append if {@code true}, then the String will be added to the
	 *               end of the file rather than overwriting
	 */
	public static void writeToFile(final File file, final String data, final boolean append){
		writeToFile(file, data, Charsets.UTF_8, append);
	}

	/**
	 * Writes a String to a file creating the file if it does not exist.
	 *
	 * @param file     the file to write
	 * @param data     the content to write to the file
	 * @param encoding the encoding to use, {@code null} means platform default
	 */
	public static void writeToFile(final File file, final String data, final Charset encoding) {
		writeToFile(file, data, encoding, false);
	}

	/**
	 * 将 String 写入创建该文件的文件（如果该文件不存在）。
	 *
	 * @param file     要写入的文件
	 * @param data     要写入文件的内容
	 * @param encoding 要使用的编码 {@code null} 表示平台默认值
	 * @param append   if {@code true}, then the String will be added to the
	 *                 end of the file rather than overwriting
	 */
	public static void writeToFile(final File file, final String data, final Charset encoding, final boolean append) {
		try (OutputStream out = new FileOutputStream(file, append)) {
			IoUtil.write(data, out, encoding);
		} catch (IOException e) {
			throw Exceptions.unchecked(e);
		}
	}

	/**
	 * 转成file
	 * @param multipartFile MultipartFile
	 * @param file File
	 */
	public static void toFile(MultipartFile multipartFile, final File file) {
		try {
			FileUtil.toFile(multipartFile.getInputStream(), file);
		} catch (IOException e) {
			throw Exceptions.unchecked(e);
		}
	}

	/**
	 * 转成file
	 * @param in InputStream
	 * @param file File
	 */
	public static void toFile(InputStream in, final File file) {
		try (OutputStream out = Files.newOutputStream(file.toPath())) {
			FileUtil.copy(in, out);
		} catch (IOException e) {
			throw Exceptions.unchecked(e);
		}
	}

	/**
	 * 移动文件
	 * <p>
	 * 如果目标文件已经存在，那么rename重命名肯定会失败，失败时则将内容复制到目标文件中，然后删掉源文件
	 * @param srcFile 源文件
	 * @param destFile 目标文件
	 */
	public static void moveFile(final File srcFile, final File destFile) throws IOException {
		Assert.notNull(srcFile, "Source must not be null");
		Assert.notNull(destFile, "Destination must not be null");
		if (!srcFile.exists()) {
			throw new FileNotFoundException("Source '" + srcFile + "' does not exist");
		}
		if (srcFile.isDirectory()) {
			throw new IOException("Source '" + srcFile + "' is a directory");
		}
		if (destFile.isDirectory()) {
			throw new IOException("Destination '" + destFile + "' is a directory");
		}
		final boolean rename = srcFile.renameTo(destFile);
		if (!rename) {
			FileUtil.copy(srcFile, destFile);
			if (!srcFile.delete()) {
				FileUtil.deleteQuietly(destFile);
				throw new IOException("Failed to delete original file '" + srcFile + "' after copy to '" + destFile + "'");
			}
		}
	}

	/**
	 * 删除文件，从不引发异常。如果 file 是一个目录，请删除它和所有子目录。
	 * <p>
	 * File.delete（） 和此方法的区别在于
	 * <ul>
	 * <li>要删除的目录不必为空.</li>
	 * <li>当无法删除文件或目录时，不会引发任何异常。</li>
	 * </ul>
	 */
	public static void deleteQuietly(@Nullable final File file) {
		if (file == null) {
			return;
		}
		try {
			if (file.isDirectory()) {
				FileSystemUtils.deleteRecursively(file);
			}
		} catch (final Exception ignored) {
		}

		try {
			boolean delete = file.delete();
			if (!delete) {
				throw new CustomException("删除文件失败");
			}
		} catch (final Exception e) {
			log.error(e.getMessage(), e);
		}
	}

	/**
	 * 构建目录树
	 */
	public static FileNode buildDirectoryTree(File file, String filterType, String filterName) {
		if (file.isFile()) {
			if (matchesFilter(file, filterType, filterName)) {
				return new FileNode(file.getName(), file.length());
			} else {
				// 文件不匹配过滤条件，返回 null
				return null;
			}
		} else {
			long totalSize = 0;
			List<FileNode> children = Arrays.stream(Optional.ofNullable(file.listFiles()).orElse(new File[0]))
					.parallel() // 并行处理提高性能
					.map(child -> buildDirectoryTree(child, filterType, filterName))
					.filter(Objects::nonNull) // 过滤掉不匹配的节点
					.collect(Collectors.toList());

			for (FileNode child : children) {
				totalSize += child.getSize();
			}

			FileNode node = new FileNode(file.getName(), totalSize);
			node.setChildren(children);
			return node;
		}
	}

	/**
	 * 判断文件是否匹配过滤条件
	 * @param file 文件类型
	 * @param filterType 过滤类型
	 * @param filterName 过滤文件名
	 * @return boolean
	 */
	private static boolean matchesFilter(File file, String filterType, String filterName) {
		if (file.isDirectory()) return true; // 目录不过滤
		String fileName = file.getName().toLowerCase();
		return (filterType.isEmpty() || fileName.endsWith(filterType.toLowerCase())) &&
				(filterName.isEmpty() || fileName.contains(filterName.toLowerCase()));
	}

	/**
	 * 打印树结构
	 * @param node 节点
	 * @param level 层级
	 * @param includeZeroSize 是否过滤大小为0的目录和文件
	 */
	public static void printTree(FileNode node, int level, boolean includeZeroSize) {
		if (!includeZeroSize && node.getSize() == 0) {
			return; // 如果不包含大小为 0 的文件或目录，则跳过
		}

		StringBuilder indent = new StringBuilder();
		for (int i = 0; i < level; i++) {
			indent.append("│   ");
		}
		System.out.println(indent + "├── " + node.getFileName() + " (" + formatSize(node.getSize()) + ")");
		if (CollUtil.isNotEmpty(node.getChildren())) {
			for (FileNode child : node.getChildren()) {
				printTree(child, level + 1, includeZeroSize);
			}
		}
	}

	/**
	 * 格式化文件大小
	 */
	private static String formatSize(long size) {
		if (size <= 0) return "0";
		final String[] units = new String[]{"B", "KB", "MB", "GB", "TB"};
		int digitGroups = (int) (Math.log10(size) / Math.log10(1024));
		return new DecimalFormat("#,##0.##").format(size / Math.pow(1024, digitGroups)) + " " + units[digitGroups];
	}

	public static void createFile(String path) {
		try {
			File desc = new File(path);

			if (!desc.getParentFile().exists()) {
				boolean mkdirResult = desc.getParentFile().mkdirs();
				if (!mkdirResult) {
					throw new CustomException("创建目录失败");
				}
			}
			if (!desc.exists()) {
				boolean newFile = desc.createNewFile();
				if (!newFile) {
					throw new CustomException("创建文件失败");
				}
			}
		}catch (Exception e) {
			throw new CustomException(e.getMessage());
		}
	}

}